package com.xiyuan.smartutils.reflect;

import com.xiyuan.smartutils.StrUtils;
import com.xiyuan.smartutils.Systems;
import com.xiyuan.smartutils.Types;
import com.xiyuan.smartutils.loader.SystemLoader;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AllPermission;
import java.security.Permissions;
import java.security.ProtectionDomain;

import static com.xiyuan.smartutils.constants.TypeConstants.*;

/**
 * @author xiyuan-lgz
 */
@SuppressWarnings({"all"})
public abstract class Unsafe {
    private static Unsafe unsafe;
    
    protected static Object junsafe;
    protected static ProtectionDomain protectionDomain;
    
    public static Unsafe init() {
        if (unsafe != null) return unsafe;
        try {
            JavaUnsafe.init();
            unsafe = JavaUnsafe.getUnsafe();
            Permissions permissions = new Permissions();
            permissions.add(new AllPermission());
            protectionDomain = new ProtectionDomain(null, permissions);
        }
        catch (Throwable e) {
            if (Systems.isDev()) {
                e.printStackTrace();
            }
        }
        
        return unsafe;
    }
    
    /**
     * 获取 sun.misc.Unsafe 或者 jdk.internal.misc.Unsafe
     *
     * @return
     */
    public static Object getJavaUnsafe() {
        return junsafe;
    }
    
    public Class<?> getUnsafeProvider() {
        return junsafe == null ? null : junsafe.getClass();
    }
    
    protected Unsafe(Object javaUnsafe) {
        this.junsafe = javaUnsafe;
    }
    
    public static void setAccessible(Constructor<?> constructor) {
        JavaUnsafe.setAccessible(constructor, true);
    }
    
    public static void setAccessible(Constructor<?> constructor, boolean flag) {
        JavaUnsafe.setAccessible(constructor, flag);
    }
    
    public static void setAccessible(Field field) {
        JavaUnsafe.setAccessible(field, true);
    }
    
    public static void setAccessible(Field field, boolean flag) {
        JavaUnsafe.setAccessible(field, flag);
    }
    
    public static void setAccessible(Method method) {
        JavaUnsafe.setAccessible(method, true);
    }
    
    public static void setAccessible(Method method, boolean flag) {
        JavaUnsafe.setAccessible(method, flag);
    }
    
    protected static void openInner(Object source, String pkg, Object target) {
        JavaUnsafe.addOpensOrExports(source, pkg, target, true);
    }
    
    /**
     * addOpens
     *
     * @param source source module
     * @param pkg    source package
     * @param target target module
     */
    public static void addOpens(Object source, String pkg, Object target) {
        addOpensOrExports(source, pkg, target, true);
    }
    
    /**
     * addOpens
     *
     * @param source source module
     * @param pkg    source package
     * @param target target module
     * @param open   true: open, false: export
     */
    public static void addOpensOrExports(Object source, String pkg, Object target, boolean open) {
        addOpensOrExports(source, pkg, target, open, true);
    }
    
    public static void addOpensOrExports(Object source, String pkg, Object target, boolean open, boolean syncVM) {
        source = unsafe.getModule(source);
        JavaUnsafe.addOpensOrExports(source, pkg, target, open, syncVM);
    }
    
    static {
        init();
    }
    
    public static Unsafe getUnsafe() {
        return unsafe;
    }
    
    public abstract int getVersion();
    
    protected abstract Object getModule(Object obj);
    
    //*********************************************************
    //****** 基础类型 设置***************************************
    //*********************************************************
    public abstract void putObject(Object obj, long offset, Object value);
    
    public abstract Object getObject(Object obj, long offset);
    
    public Object getObject(long offset) {return getObject(null, offset);}
    
    public abstract void putBoolean(Object obj, long offset, boolean value);
    
    public abstract boolean getBoolean(Object obj, long address);
    
    public boolean getBoolean(long address) {return getBoolean(null, address);}
    
    public abstract void putByte(Object obj, long offset, byte value);
    
    public void putByte(long address, byte x) {
        putByte(null, address, x);
    }
    
    public abstract byte getByte(Object obj, long address);
    
    public byte getByte(long address) {return getByte(null, address);}
    
    public abstract void putChar(Object obj, long offset, char value);
    
    public abstract char getChar(Object obj, long offset);
    
    public char getChar(long offset) {return getChar(null, offset);}
    
    public abstract void putShort(Object obj, long offset, short value);
    
    public abstract short getShort(Object obj, long offset);
    
    public short getShort(long offset) {return getShort(null, offset);}
    
    public abstract void putInt(Object obj, long offset, int value);
    
    public abstract int getInt(Object obj, long offset);
    
    public int getInt(long offset) {return getInt(null, offset);}
    
    public abstract void putLong(Object obj, long offset, long value);
    
    public abstract long getLong(Object obj, long offset);
    
    public long getLong(long offset) {return getLong(null, offset);}
    
    public abstract void putFloat(Object obj, long offset, float value);
    
    public abstract float getFloat(Object obj, long offset);
    
    public float getFloat(long offset) {return getFloat(null, offset);}
    
    
    public abstract void putDouble(Object obj, long offset, double value);
    
    public abstract double getDouble(Object obj, long offset);
    
    public double getDouble(long offset) {return getDouble(null, offset);}
    
    
    /**
     * Disposes of a block of native memory, as obtained from {@link
     * #allocateMemory} or {@link #reallocateMemory}.  The address passed to
     * this method may be null, in which case no action is taken.
     * <p>
     * <em>Note:</em> It is the responsibility of the caller to make
     * sure arguments are checked before the methods are called. While
     * some rudimentary checks are performed on the input, the checks
     * are best effort and when performance is an overriding priority,
     * as when methods of this class are optimized by the runtime
     * compiler, some or all checks (if any) may be elided. Hence, the
     * caller must not rely on the checks and corresponding
     * exceptions!
     *
     * @throws RuntimeException if any of the arguments is invalid
     * @see #allocateMemory
     */
    public abstract void freeMemory(long address);
    
    
    /**
     * Allocates a new block of native memory, of the given size in bytes.  The
     * contents of the memory are uninitialized; they will generally be
     * garbage.  The resulting native pointer will never be zero, and will be
     * aligned for all value types.  Dispose of this memory by calling {@link
     * #freeMemory}, or resize it with {@link #reallocateMemory}.
     * <p>
     * <em>Note:</em> It is the responsibility of the caller to make
     * sure arguments are checked before the methods are called. While
     * some rudimentary checks are performed on the input, the checks
     * are best effort and when performance is an overriding priority,
     * as when methods of this class are optimized by the runtime
     * compiler, some or all checks (if any) may be elided. Hence, the
     * caller must not rely on the checks and corresponding
     * exceptions!
     *
     * @throws RuntimeException if the size is negative or too large
     *                          for the native size_t type
     * @throws OutOfMemoryError if the allocation is refused by the system
     * @see #getByte(long)
     * @see #putByte(long, byte)
     */
    public abstract long allocateMemory(long bytes);
    
    
    /**
     * Resizes a new block of native memory, to the given size in bytes.  The
     * contents of the new block past the size of the old block are
     * uninitialized; they will generally be garbage.  The resulting native
     * pointer will be zero if and only if the requested size is zero.  The
     * resulting native pointer will be aligned for all value types.  Dispose
     * of this memory by calling {@link #freeMemory}, or resize it with {@link
     * #reallocateMemory}.  The address passed to this method may be null, in
     * which case an allocation will be performed.
     * <p>
     * <em>Note:</em> It is the responsibility of the caller to make
     * sure arguments are checked before the methods are called. While
     * some rudimentary checks are performed on the input, the checks
     * are best effort and when performance is an overriding priority,
     * as when methods of this class are optimized by the runtime
     * compiler, some or all checks (if any) may be elided. Hence, the
     * caller must not rely on the checks and corresponding
     * exceptions!
     *
     * @throws RuntimeException if the size is negative or too large
     *                          for the native size_t type
     * @throws OutOfMemoryError if the allocation is refused by the system
     * @see #allocateMemory
     */
    public abstract long reallocateMemory(long address, long bytes);
    
    
    /**
     * Sets all bytes in a given block of memory to a fixed value
     * (usually zero).  This provides a <em>single-register</em> addressing mode,
     * as discussed in {@link #getInt(Object, long)}.
     *
     * <p>Equivalent to {@code setMemory(null, address, bytes, value)}.
     */
    public void setMemory(long address, long bytes, byte value) {
        setMemory(null, address, bytes, value);
    }
    
    /**
     * Sets all bytes in a given block of memory to a fixed value (usually zero).
     * This method determines a block's base address by means of two parameters, and so it provides (in effect) a double-register addressing mode, as discussed in getInt(Object, long). When the object reference is null, the offset supplies an absolute base address.
     * The stores are in coherent (atomic) units of a size determined by the address and length parameters. If the effective address and length are all even modulo 8, the stores take place in 'long' units. If the effective address and length are (resp.) even modulo 4 or 2, the stores take place in units of 'int' or 'short'. Note: It is the responsibility of the caller to make sure arguments are checked before the methods are called. While some rudimentary checks are performed on the input, the checks are best effort and when performance is an overriding priority, as when methods of this class are optimized by the runtime compiler, some or all checks (if any) may be elided. Hence, the caller must not rely on the checks and corresponding exceptions!
     * Throws:
     * RuntimeException – if any of the arguments is invalid
     *
     * @param obj
     * @param offset
     * @param bytes
     * @param value
     */
    public abstract void setMemory(Object obj, long offset, long bytes, byte value);
    
    
    /**
     * Sets all bytes in a given block of memory to a copy of another
     * block.  This provides a <em>single-register</em> addressing mode,
     * as discussed in {@link #getInt(Object, long)}.
     * <p>
     * Equivalent to {@code copyMemory(null, srcAddress, null, destAddress, bytes)}.
     */
    public void copyMemory(long srcAddress, long destAddress, long bytes) {
        copyMemory(null, srcAddress, null, destAddress, bytes);
    }
    
    public abstract void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
    
    /**
     * Reports the size in bytes of a native memory page (whatever that is). This value will always be a power of two
     *
     * @return
     */
    public abstract int pageSize();
    
    /**
     * Unblocks the given thread blocked on park, or, if it is not blocked, causes the subsequent call to park not to block. Note: this operation is "unsafe" solely because the caller must somehow ensure that the thread has not been destroyed. Nothing special is usually required to ensure this when called from Java (in which there will ordinarily be a live reference to the thread) but this is not nearly-automatically so when calling from native code.
     *
     * @param o
     */
    public abstract void unpark(Object o);
    
    /**
     * Blocks current thread, returning when a balancing unpark occurs, or a balancing unpark has already occurred, or the thread is interrupted, or, if not absolute and time is not zero, the given time nanoseconds have elapsed, or if absolute, the given deadline in milliseconds since Epoch has passed, or spuriously (i.e., returning for no "reason"). Note: This operation is in the Unsafe class only because unpark is, so it would be strange to place it elsewhere.
     *
     * @param isAbsolute
     * @param time
     */
    public abstract void park(boolean isAbsolute, long time);
    
    /**
     * Gets the load average in the system run queue assigned
     * to the available processors averaged over various periods of time.
     * This method retrieves the given {@code nelem} samples and
     * assigns to the elements of the given {@code loadavg} array.
     * The system imposes a maximum of 3 samples, representing
     * averages over the last 1,  5,  and  15 minutes, respectively.
     *
     * @param loadavg an array of double of size nelems
     * @param nelems  the number of samples to be retrieved and
     *                must be 1 to 3.
     * @return the number of samples actually retrieved; or -1
     * if the load average is unobtainable.
     */
    public abstract int getLoadAverage(double[] loadavg, int nelems);
    
    /**
     * Ensures that loads before the fence will not be reordered with loads and stores after the fence; a "LoadLoad plus LoadStore barrier". Corresponds to C11 atomic_thread_fence(memory_order_acquire) (an "acquire fence"). A pure LoadLoad fence is not provided, since the addition of LoadStore is almost always desired, and most current hardware instructions that provide a LoadLoad barrier also provide a LoadStore barrier for free.
     */
    public abstract void loadFence();
    
    /**
     * Ensures that loads and stores before the fence will not be reordered with stores after the fence; a "StoreStore plus LoadStore barrier". Corresponds to C11 atomic_thread_fence(memory_order_release) (a "release fence"). A pure StoreStore fence is not provided, since the addition of LoadStore is almost always desired, and most current hardware instructions that provide a StoreStore barrier also provide a LoadStore barrier for free.
     */
    public abstract void storeFence();
    
    /**
     * Ensures that loads and stores before the fence will not be reordered with loads and stores after the fence. Implies the effects of both loadFence() and storeFence(), and in addition, the effect of a StoreLoad barrier. Corresponds to C11 atomic_thread_fence(memory_order_seq_cst).
     */
    public abstract void fullFence();
    
    
    /**
     * Reports the location of a given field in the storage allocation of its
     * class.  Do not expect to perform any sort of arithmetic on this offset;
     * it is just a cookie which is passed to the unsafe heap memory accessors.
     *
     * <p>Any given field will always have the same offset and base, and no
     * two distinct fields of the same class will ever have the same offset
     * and base.
     *
     * <p>As of 1.4.1, offsets for fields are represented as long values,
     * although the Sun JVM does not use the most significant 32 bits.
     * However, JVM implementations which store static fields at absolute
     * addresses can use long offsets and null base pointers to express
     * the field locations in a form usable by {@link #getInt(Object, long)}.
     * Therefore, code which will be ported to such JVMs on 64-bit platforms
     * must preserve all bits of static field offsets.
     *
     * @see #getInt(Object, long)
     */
    public abstract long objectFieldOffset(Field f);
    
    /**
     * Reports the location of a given static field, in conjunction with {@link
     * #staticFieldBase}.
     * <p>Do not expect to perform any sort of arithmetic on this offset;
     * it is just a cookie which is passed to the unsafe heap memory accessors.
     *
     * <p>Any given field will always have the same offset, and no two distinct
     * fields of the same class will ever have the same offset.
     *
     * <p>As of 1.4.1, offsets for fields are represented as long values,
     * although the Sun JVM does not use the most significant 32 bits.
     * It is hard to imagine a JVM technology which needs more than
     * a few bits to encode an offset within a non-array object,
     * However, for consistency with other methods in this class,
     * this method reports its result as a long value.
     *
     * @see #getInt(Object, long)
     */
    public abstract long staticFieldOffset(Field f);
    
    /**
     * Reports the location of a given static field, in conjunction with {@link
     * #staticFieldOffset}.
     * <p>Fetch the base "Object", if any, with which static fields of the
     * given class can be accessed via methods like {@link #getInt(Object,
     * long)}.  This value may be null.  This value may refer to an object
     * which is a "cookie", not guaranteed to be a real Object, and it should
     * not be used in any way except as argument to the get and put routines in
     * this class.
     */
    public abstract Object staticFieldBase(Field f);
    
    /**
     * Detects if the given class may need to be initialized. This is often
     * needed in conjunction with obtaining the static field base of a
     * class.
     *
     * @return false only if a call to {@code ensureClassInitialized} would have no effect
     */
    public abstract boolean shouldBeInitialized(Class<?> c);
    
    /**
     * Ensures the given class has been initialized. This is often
     * needed in conjunction with obtaining the static field base of a
     * class.
     */
    public abstract void ensureClassInitialized(Class<?> c);
    
    /**
     * Reports the offset of the first element in the storage allocation of a
     * given array class.  If {@link #arrayIndexScale} returns a non-zero value
     * for the same class, you may use that scale factor, together with this
     * base offset, to form new offsets to access elements of arrays of the
     * given class.
     *
     * @see #getInt(Object, long)
     * @see #putInt(Object, long, int)
     */
    public abstract int arrayBaseOffset(Class<?> arrayClass);
    
    /**
     * Reports the scale factor for addressing elements in the storage
     * allocation of a given array class.  However, arrays of "narrow" types
     * will generally not work properly with accessors like {@link
     * #getByte(Object, long)}, so the scale factor for such classes is reported
     * as zero.
     *
     * @see #arrayBaseOffset
     * @see #getInt(Object, long)
     * @see #putInt(Object, long, int)
     */
    public abstract int arrayIndexScale(Class<?> arrayClass);
    
    // public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5,
    //                                      ProtectionDomain var6);
    //
    // public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
    //
    
    
    /**
     * 定义类
     */
    public abstract <T> Class<T> defineClass(String name, byte[] bytes, int off, int len, ClassLoader loader,
                                             ProtectionDomain protectionDomain);
    
    public <T> Class<T> defineClass(String name, byte[] bytes, ClassLoader loader) {
        return defineClass(name, bytes, 0, bytes.length, loader, protectionDomain);
    }
    
    public <T> Class<T> defineClass(byte[] bytes, ClassLoader loader) {
        return defineClass(null, bytes, 0, bytes.length, loader, protectionDomain);
    }
    
    /**
     * 创建对象不会初始化
     *
     * @param var1
     * @return
     * @throws InstantiationException
     */
    public abstract Object allocateInstance(Class<?> clazz) throws InstantiationException;
    
    /**
     * 创建对象 并初始化
     *
     * @param var1
     * @param args
     * @param <T>
     * @return
     * @throws InstantiationException
     */
    public Object newInstance(Class<?> clazz) {
        if (clazz == null) return null;
        int hashCode = Types.getClassHashCode(clazz);
        switch (hashCode) {
            case BOOLEAN_CODE:
                return false;
            case BOOLEAN_CODE_OBJ:
                return Boolean.FALSE;
            case BYTE_CODE:
            case BYTE_CODE_OBJ:
                return (byte) 0;
            
            case CHAR_CODE:
            case CHAR_CODE_OBJ:
                return (char) 0;
            
            case SHORT_CODE:
            case SHORT_CODE_OBJ:
                return (short) 0;
            
            case INT_CODE:
            case INT_CODE_OBJ:
                return (int) 0;
            
            case LONG_CODE:
            case LONG_CODE_OBJ:
                return (long) 0;
            
            case FLOAT_CODE:
            case FLOAT_CODE_OBJ:
                return (float) 0;
            
            case DOUBLE_CODE:
            case DOUBLE_CODE_OBJ:
                return (double) 0;
            
            // 数组
            case BOOLEAN_CODE_ARR:
                return EMPTY_BOOL_ARR;
            case BOOLEAN_CODE_OBJ_ARR:
                return EMPTY_BOOL_ARR_OBJ;
            case BYTE_CODE_ARR:
                return EMPTY_BYTE_ARR;
            case BYTE_CODE_OBJ_ARR:
                return EMPTY_BYTE_ARR_OBJ;
            
            case CHAR_CODE_ARR:
                return EMPTY_CHAR_ARR;
            case CHAR_CODE_OBJ_ARR:
                return EMPTY_CHAR_ARR_OBJ;
            
            case SHORT_CODE_ARR:
                return EMPTY_SHORT_ARR;
            case SHORT_CODE_OBJ_ARR:
                return EMPTY_SHORT_ARR_OBJ;
            
            case INT_CODE_ARR:
                return EMPTY_INT_ARR;
            case INT_CODE_OBJ_ARR:
                return EMPTY_INT_ARR_OBJ;
            
            case LONG_CODE_ARR:
                return EMPTY_LONG_ARR;
            case LONG_CODE_OBJ_ARR:
                return EMPTY_LONG_ARR_OBJ;
            
            case FLOAT_CODE_ARR:
                return EMPTY_FLOAT_ARR;
            case FLOAT_CODE_OBJ_ARR:
                return EMPTY_FLOAT_ARR_OBJ;
            
            case DOUBLE_CODE_ARR:
                return EMPTY_DOUBLE_ARR;
            case DOUBLE_CODE_OBJ_ARR:
                return EMPTY_DOUBLE_ARR_OBJ;
            default:
                if (clazz.isArray()) {
                    return Array.newInstance(clazz.getComponentType(), 0);
                }
                try {
                    return allocateInstance(clazz);
                }
                catch (Exception e) {
                    throw new RuntimeException(e);
                }
            
        }
    }
    
    
}
